﻿using Microsoft.Extensions.Configuration;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Diagnostics;
using System.Text.Json;
using System.Threading;

namespace xu.TinyDBConfigProvider
{
    internal class TinyDBConfigurationProvider : ConfigurationProvider, IDisposable
    {
        private readonly TinyDBConfigOptions options;

        // 允许并发读，不允许并发写
        private readonly ReaderWriterLockSlim lockObj = new ReaderWriterLockSlim();

        private bool isDisposed = false;

        public TinyDBConfigurationProvider(TinyDBConfigOptions options)
        {
            this.options = options;

            TimeSpan interval = TimeSpan.FromSeconds(3);

            if (options.ReloadInterval != null) { interval = options.ReloadInterval.Value; }

            if (options.ReloadOnChange)
            {
                ThreadPool.QueueUserWorkItem(xu =>
                {
                    while (!isDisposed)
                    {
                        Load();
                        Thread.Sleep(interval);
                    }
                });
            }
        }

        public override IEnumerable<string> GetChildKeys(IEnumerable<string> earlierKeys, string parentPath)
        {
            lockObj.EnterReadLock();
            try
            {
                return base.GetChildKeys(earlierKeys, parentPath);
            }
            finally
            {
                lockObj.ExitReadLock();
            }
        }

        public override bool TryGet(string key, out string value)
        {
            lockObj.EnterReadLock();
            try
            {
                return base.TryGet(key, out value);
            }
            finally
            {
                lockObj.ExitReadLock();
            }
        }

        public override void Load()
        {
            base.Load();
            IDictionary<string, string> clonedData = null;
            try
            {
                lockObj.EnterWriteLock();
                clonedData = Data.Clone();
                string tableName = options.TableName;
                Data.Clear();
                using (System.Data.IDbConnection conn = options.CreateDbConnection())
                {
                    conn.Open();
                    DoLoad(tableName, conn);
                }
            }
            catch (DbException)
            {
                // 如果抛出DbException，则恢复到原始数据。
                this.Data = clonedData;
                throw;
            }
            finally
            {
                lockObj.ExitWriteLock();
            }
            // OnReload不能介于EnterWriteLock和ExitWriteLock之间，否则将抛出“在此模式下保持写锁时可能无法获取读锁”。
            if (CommonTool.IsChanged(clonedData, Data))
            {
                OnReload();
            }
        }

        public void Dispose()
        {
            this.isDisposed = true;
        }

        private void DoLoad(string tableName, System.Data.IDbConnection conn)
        {
            using (System.Data.IDbCommand cmd = conn.CreateCommand())
            {
                cmd.CommandText = $"SELECT KeyName, KeyValue FROM {tableName} WHERE Id IN(SELECT Max(Id) FROM {tableName} GROUP BY KeyName)";
                using (System.Data.IDataReader reader = cmd.ExecuteReader())
                {
                    while (reader.Read())
                    {
                        string name = reader.GetString(0);
                        string value = reader.GetString(1);
                        if (value == null)
                        {
                            this.Data[name] = value;
                            continue;
                        }
                        value = value.Trim();
                        // 如果值类似于[…] 或者 {}，可能是json数组值或json对象值，尝试解析为 json
                        if (value.StartsWith("[") && value.EndsWith("]") || value.StartsWith("{") && value.EndsWith("}"))
                        {
                            TryLoadAsJson(name, value);
                        }
                        else
                        {
                            this.Data[name] = value;
                        }
                    }
                }
            }
        }

        private void LoadJsonElement(string name, JsonElement jsonRoot)
        {
            if (jsonRoot.ValueKind == JsonValueKind.Array)
            {
                int index = 0;
                foreach (JsonElement item in jsonRoot.EnumerateArray())
                {
                    // "a:b:0"="hello";"a:b:1"="world" // YAML
                    string path = name + ConfigurationPath.KeyDelimiter + index;
                    LoadJsonElement(path, item);
                    index++;
                }
            }
            else if (jsonRoot.ValueKind == JsonValueKind.Object)
            {
                foreach (JsonProperty jsonObj in jsonRoot.EnumerateObject())
                {
                    string pathOfObj = name + ConfigurationPath.KeyDelimiter + jsonObj.Name;
                    LoadJsonElement(pathOfObj, jsonObj.Value);
                }
            }
            else
            {
                this.Data[name] = jsonRoot.GetValueForConfig();
            }
        }

        private void TryLoadAsJson(string name, string value)
        {
            JsonDocumentOptions jsonOptions = new JsonDocumentOptions { AllowTrailingCommas = true, CommentHandling = JsonCommentHandling.Skip };
            try
            {
                JsonElement jsonRoot = JsonDocument.Parse(value, jsonOptions).RootElement;
                LoadJsonElement(name, jsonRoot);
            }
            catch (JsonException ex)
            {
                // 如果不是有效的json，则解析为普通字符串值
                this.Data[name] = value;
                Debug.WriteLine($"将 {value} 转换为 JSON对象 时产生异常. {ex}");
            }
        }
    }
}